package fr.labri.harmony.analysis.jsep;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.text.DecimalFormat;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import fr.labri.harmony.core.analysis.AbstractAnalysis;
import fr.labri.harmony.core.config.model.AnalysisConfiguration;
import fr.labri.harmony.core.dao.Dao;
import fr.labri.harmony.core.model.Author;
import fr.labri.harmony.core.model.Event;
import fr.labri.harmony.core.model.Source;
import fr.labri.harmony.core.source.WorkspaceException;

public class ToyAnalysis extends AbstractAnalysis {

	AtomicInteger cpt = new AtomicInteger();
	
	static final int MIN_COMMIT = 10;
	static final int MIN_MONTH = 1;
	static final int MIN_CLOC = 100;
	
	public ToyAnalysis() {
		super();
	}

	public ToyAnalysis(AnalysisConfiguration config, Dao dao, Properties properties) {
		super(config, dao, properties);
	}

	@Override
	public void runOn(Source src) {

		List<Event> events = linearizeHistory(src);
		if(events.size() < MIN_COMMIT)
			return;
		int nbM = getNumberOfMonths(events.get(0).getTimestamp(), events.get(events.size()-1).getTimestamp());
		if(nbM < MIN_MONTH) 
			return;
		String workdir = src.getWorkspace().getPath();
		int cloc = getJavaCloc(workdir);
		if(cloc  < MIN_CLOC) 
			return;
		
		try {
			FileWriter fw = new FileWriter("jsep_project_nottoy.txt",true);
			fw.append(src.getUrl()+"\n");
			fw.close();
		}
		catch(IOException e){}

	}

	private List<Event> linearizeHistory(Source src) {
		List<Event> events = new ArrayList<Event>(src.getEvents());
		Collections.sort(events, new EventComparator());

		List<Event> oneBranch = new ArrayList<Event>();

		Event head = (Event) events.get(events.size() - 1);
		oneBranch.add((Event) events.get(events.size() - 1));
		Event par;
		while (!head.getParents().isEmpty()) {
			if (head.getParents().size() > 1) {
				int size = -1;
				List<Event> parts = new ArrayList<Event>(head.getParents());
				int max = 0;
				for (int i = 0; i < parts.size(); i++) {
					par = (Event) parts.get(i);
					if (size == -1) {
						size = head.getActions(par).size();
					} else if (size > head.getActions(par).size()) {
						max = 1;
					}
				}

				oneBranch.add((Event) parts.get(max));
				head = (Event) parts.get(max);
			} else {
				Event t = (Event) head.getParents().iterator().next();
				oneBranch.add(t);
				head = t;
			}
		}

		Collections.reverse(oneBranch);
		return oneBranch;
	}

	class EventComparator implements Comparator<Event> {

		@Override
		public int compare(Event arg0, Event arg1) {
			return Long.compare(arg0.getTimestamp(), arg1.getTimestamp());
		}	
	}

	private static String formatDouble(double s) {
		return new DecimalFormat("#.##").format(s);
	}

	private int getJavaCloc(String directory) {
		try {
			Process p = Runtime.getRuntime().exec(new String[] { "cloc", new File(directory).getAbsolutePath(), "--xml", "--quiet", "--force-lang=\"JAVA\"" });
			BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
			StringBuffer b = new StringBuffer();
			String line;
			while ((line = r.readLine()) != null) {
				if (line.trim().startsWith("<")) b.append(line + "\n");
			}
			p.waitFor();
			r.close();
			
			if(b.toString().isEmpty())
				return 0;
			
			DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = df.newDocumentBuilder();

			Document doc = db.parse(new InputSource(new StringReader(b.toString().trim())));
			NodeList l = doc.getElementsByTagName("language");

			if(l.getLength()>0){
				Node el = l.item(0);
				final NamedNodeMap attributes = el.getAttributes();
				return Integer.parseInt(attributes.getNamedItem("code").getTextContent());
			}
			p.waitFor();
		} catch (Exception ex) {
			throw new WorkspaceException(ex);
		}
		return 0;
	}

	private int getNumberOfMonths(long start, long end) {
		Calendar startCalendar = new GregorianCalendar();
		startCalendar.setTime(new Date(start*1000));
		Calendar endCalendar = new GregorianCalendar();
		endCalendar.setTime(new Date(end*1000));

		int diffYear = endCalendar.get(Calendar.YEAR) - startCalendar.get(Calendar.YEAR);
		int diffMonth = diffYear * 12 + endCalendar.get(Calendar.MONTH) - startCalendar.get(Calendar.MONTH);
		return diffMonth;
	}

	/*
	 * Return the number of developers - identities have been merged
	 */
	private int getNumberOfAuthors(Source src) {
		List<Author> authors = src.getAuthors();
		List<Author> copy = new ArrayList<Author>();

		for(Author dev : authors) {
			String name = clean(dev.getName());
			dev.setName(name);
		}

		int newSize = authors.size();
		int oldSize = 0;
		while(oldSize != newSize) {
			for(int i = 0 ; i < authors.size() ; i++) {
				Author dev = authors.get(i);
				for(int j = i ; j < authors.size() ; j++) {
					Author dev2 = authors.get(j);
					if(dev.getId()!=dev2.getId()) {
						if(dev.getName().equals(dev2.getName())) {
							copy.add(dev);
							copy.add(dev2);
							break;
						}
						else {
							String s1 = dev.getEmail().toLowerCase().replaceAll("\\.", "").replaceAll("\\-", "");
							String s2 = dev2.getEmail().toLowerCase().replaceAll("\\.", "").replaceAll("\\-", "");

							if(s1.contains("@") && s2.contains("@")) {
								s1 = s1.substring(0,s1.indexOf("@"));
								s2 = s2.substring(0,s2.indexOf("@"));
								if(s1.equals(s2)){
									copy.add(dev);
									copy.add(dev2);
									break;
								}
							}

						}
					}
				}
			}
			oldSize = authors.size();
			for(int j = 0 ; j < copy.size() ; j=j+2 ){
				if(copy.get(j).getName().length() > copy.get(j+1).getName().length()) {
					authors.remove(copy.get(j+1));
				}
				else {
					authors.remove(copy.get(j));
				}
			}
			newSize = authors.size();
		}
		return authors.size();
	}

	private String clean(String name) {
		String n = "";
		name = Normalizer.normalize(name, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
		name = name.replaceAll("\\_", "\\.").replaceAll("\\?", "");
		for(int i = 0 ; i < name.length() ; i++)
			if(i > 0 && name.charAt(i-1)==' ')
				n+= Character.toUpperCase(name.charAt(i));
			else
				n+= name.charAt(i);
		return n;
	}
}
